package com.tinyproxy.common;

import com.tinyproxy.TinyProxy;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

/**
 * TinyProxy工具类
 */
public class Kits {

    public static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    public static final int RESP_MID = 7889;// 大于该值成功，否走都是失败

    public static int readInt(ByteBuf buffer) {
        int raw = buffer.readInt();
        int ret = 0;
        ret |= 0b11111111111000000000000000000000 & (raw << 11);
        ret |= 0b00000000000111111111100000000000 & (raw << 10);
        ret |= 0b00000000000000000000011111111111 & (raw >> 21);
        return ret;
    }

    public static void writeInt(ByteBuf buf, int v) {
        int ret = 0;
        ret |= 0b00000000000111111111110000000000 & (v >> 11);
        ret |= 0b00000000000000000000001111111111 & (v >> 10);
        ret |= 0b11111111111000000000000000000000 & (v << 21);
        buf.writeInt(ret);
    }

    public static boolean equalsMem(byte[] b1, byte[] b2, int lo1, int lo2, int size) {
        if (b1 == null || b2 == null || lo1 < 0 || lo2 < 0 || size < 0) {
            return false;
        }
        if (b1.length < lo1 + size || b2.length < lo2 + size) {
            return false;
        }
        for (; size > 0; size--) {
            if (b1[lo1++] != b2[lo2++]) {
                return false;
            }
        }
        return true;
    }

    public static byte[] toSha256(byte[]... bytes) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            for (byte[] b : bytes) {
                digest.update(b);
            }
            return digest.digest();
        } catch (Exception e) {
            return new byte[0];// 不可能!!
        }
    }

    public static byte[] asBytes(String s) {
        if (s == null || s.isEmpty()) {
            return new byte[0];
        }
        return s.getBytes(StandardCharsets.UTF_8);
    }

    public static String toString(byte[] b) {
        if (b == null || b.length == 0) {
            return "";
        }
        return new String(b, StandardCharsets.UTF_8);
    }

    public static void encrypt(byte[] token, byte[] raw) {
        encrypt(token, raw, 0, raw.length);
    }

    public static int encrypt(byte[] token, ByteBuf raw, int iToken, int size) {
        if (token == null || raw == null || token.length == 0 || size == 0) {
            return iToken;
        }

        int it = iToken;
        int p = raw.readerIndex();
        for (int i = 0; i < size; i++, p++) {
            byte t = token[it];
            it++;
            if (it >= token.length) {
                it = 0;
            }

            int v = (raw.getByte(p) ^ t) & 0x0ff;
            int hi = (v << 3) & 0b011111000;
            int lo = (v >> 5) & 0b000000111;
            raw.setByte(p, (byte) (lo | hi));
        }
        return it;
    }

    public static int encrypt(byte[] token, byte[] raw, int iToken, int size) {
        if (token == null || raw == null || token.length == 0 || raw.length == 0 || size == 0) {
            return iToken;
        }

        int it = iToken;
        for (int i = 0; i < size; i++) {
            byte t = token[it];
            it++;
            if (it >= token.length) {
                it = 0;
            }

            int v = (raw[i] ^ t) & 0x0ff;
            int hi = (v << 3) & 0b011111000;
            int lo = (v >> 5) & 0b000000111;
            raw[i] = (byte) (lo | hi);
        }
        return it;
    }

    public static void decrypt(byte[] token, byte[] encrypted) {
        decrypt(token, encrypted, 0, encrypted.length);
    }

    public static int decrypt(byte[] token, ByteBuf encrypted, int iToken, int size) {
        if (token == null || encrypted == null || token.length == 0 || size < 1) {
            return iToken;
        }

        int p = encrypted.readerIndex();
        int it = iToken;
        for (int i = 0; i < size; i++, p++) {
            byte t = token[it];
            it++;
            if (it >= token.length) {
                it = 0;
            }

            byte v = encrypted.getByte(p);
            int hi = (v << 5) & 0b011100000;
            int lo = (v >> 3) & 0b000011111;
            v = (byte) (((lo | hi) ^ t) & 0x0ff);
            encrypted.setByte(p, v);
        }
        return it;
    }

    public static int decrypt(byte[] token, byte[] encrypted, int iToken, int size) {
        if (token == null || encrypted == null || token.length == 0 || encrypted.length == 0 || size < 1) {
            return iToken;
        }

        int it = iToken;
        for (int i = 0; i < size; i++) {
            byte t = token[it];
            it++;
            if (it >= token.length) {
                it = 0;
            }

            int v = encrypted[i];
            int hi = (v << 5) & 0b011100000;
            int lo = (v >> 3) & 0b000011111;
            v = lo | hi;
            encrypted[i] = (byte) ((v ^ t) & 0x0ff);
        }
        return it;
    }

    public static boolean isBlank(String s) {
        if (s == null) {
            return true;
        }
        int lo = 0;
        int hi = s.length() - 1;
        while (lo <= hi && s.charAt(lo) <= ' ') {
            lo++;
        }
        while (lo <= hi && s.charAt(hi) <= ' ') {
            hi--;
        }

        return hi < lo;
    }

    /**
     * Closes the specified channel after all queued write requests are flushed.
     */
    public static void closeOnFlush(Channel ch) {
        if (ch.isActive()) {
            ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    }

    public static long toLong(String s, long defaultValue) {
        if (isBlank(s)) {
            return defaultValue;
        }
        try {
            return Long.parseLong(s);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static int toInt(String s, int defaultValue) {
        if (isBlank(s)) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(s);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    static String EXEC_PREFIX;

    /**
     * @return 获取应用可执行程序名称无后缀
     */
    public static String executablePrefix() {
        if (EXEC_PREFIX == null) {
            try {
                URI uri = TinyProxy.class.getProtectionDomain().getCodeSource().getLocation().toURI();
                File dir = new File(uri).getParentFile();
                EXEC_PREFIX = new File(dir, TinyProxy.class.getSimpleName()).getAbsolutePath();
            } catch (URISyntaxException e) {
                EXEC_PREFIX = "";
            }
        }
        return EXEC_PREFIX;
    }

}
